/*
 * This file is part of Advanced ABX
 *
 * Copyright 2010 Christian Uebber
 *
 * Advanced ABX is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 *
 * Advanced ABX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Advanced ABX.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * OutInFader.java
 *
 *  This file is part of the NilPop package.
 */
package de.uebber.sound.lib;

import javax.sound.sampled.AudioFormat;
import java.util.Locale;

/**
 * Transition consisting of a fade out of selectable type followed
 * immediately by a symmetric fade in. For even sample counts the
 * zero transition has a length of 2, for uneven sample counts it
 * has a length of 1.
 */
public class OutInFader implements Transitioner {

    private float ditherBits = 0.3f;

    private TypeOfFade type;

    public OutInFader() {
        type = TypeOfFade.PARABOLIC;
    }

    @Override
    public void process(byte[] active, byte[] next, int srcOffset, byte[] result, int dstOffset,
                        int frameCount, AudioFormat format) {
        // Create arrays of float arrays (one for each channel) to hold samples
        float[][] lastFloatBuffer = new float[format.getChannels()][frameCount];
        float[][] nextFloatBuffer = new float[format.getChannels()][frameCount];
        // Read byte arrays into float arrays
        FloatSampleTools.byte2float(active, srcOffset, lastFloatBuffer, 0, frameCount, format);
        FloatSampleTools.byte2float(next, srcOffset, nextFloatBuffer, 0, frameCount, format);

        // Special treatment for uneven numbers of samples
        int overlap = frameCount % 2 == 0 ? 0 : 1;

        // Fade out last buffer
        int len = frameCount / 2 + overlap;
        for (float[] f : lastFloatBuffer) {
            for (int i = 0; i < len; i++) {
                f[i] = fadeOut(f[i], i, len - 1);
            }
        }

        // Fade in next buffer
        for (float[] f : nextFloatBuffer) {
            for (int i = 0; i < len; i++) {
                f[i + len - overlap] = fadeIn(f[i + len - overlap], i, len - 1);
            }
        }

        // Write fade into lastFloatBuffer to destination
        FloatSampleTools.float2byte(lastFloatBuffer, 0, result, dstOffset, len, format, ditherBits);
        // Write fade-out from nextFloatBuffer to destination
        FloatSampleTools.float2byte(nextFloatBuffer, len - overlap,
                result, (len - overlap) * format.getFrameSize() + dstOffset, len, format, ditherBits);
    }

    private float fadeIn(float sample, int index, int range) {
        switch (type) {
            case TRIANGULAR:
                return sample * index / range;
            case PARABOLIC:
                float f = (float) index / range;
                return sample * f * f;
        }
        return -1000;
    }

    private float fadeOut(float sample, int index, int range) {
        switch (type) {
            case TRIANGULAR:
                return sample * ((float) index / range * -1f + 1);
            case PARABOLIC:
                float f = 1 - ((float) index / range);
                return sample * f * f;
        }
        return -1000;
    }

    @Override
    public String getName() {
        return "OutInFader - © C.U.";
    }

    @Override
    public String getParameters() {
        String typeStr = "";
        switch (type) {
            case TRIANGULAR:
                typeStr = "Triangular (linear) shape";
                break;
            case PARABOLIC:
                typeStr = "Parabolic shape";
                break;
        }

        return String.format(Locale.US, typeStr + " - Dither bits: %.1f", ditherBits);
    }

    public TypeOfFade getTypeOfFade(TypeOfFade type) {
        return type;
    }


    public void setTypeOfFade(TypeOfFade type) {
        this.type = type;
    }

    public float getDitherBits() {
        return ditherBits;
    }

    public void setDitherBits(float ditherBits) {
        this.ditherBits = ditherBits;
    }

    public enum TypeOfFade {
        TRIANGULAR, PARABOLIC
    }
}
